import sqlite3
from LogManager import log_output
from typing import Tuple, Dict, List
import random


# Database
DATABASE = './database/user_data.db'


class LoginInfo:
    def __init__(self, username: str, password: str, user_id: int, token: str):
        self.username: str = username
        self.password: str = password
        self.user_id: int = user_id
        self.token: str = token


class UserManager:
    def __init__(self):
        self.database = DATABASE
        self.conn = None

        self.user_cnt = self.get_user_cnt()
        log_output(f'User: {self.user_cnt} users found')

    def get_conn(self):
        self.conn = sqlite3.connect(self.database)
        return self.conn

    def close_conn(self):
        if self.conn is None:
            return

        try:
            self.conn.close()
        except Exception as err:
            log_output(str(err))

        self.conn = None

    def get_user_cnt(self):
        try:
            sql = f'SELECT COUNT(*) FROM user_pwd'
            conn = self.get_conn()
            cursor = conn.cursor()
            rows = cursor.execute(sql)

            result = None
            for row in rows:
                result = row[0]

            if result is None:
                raise ValueError()

            return result
        except Exception as err:
            log_output(str(err))
            raise ValueError()
        finally:
            if cursor is not None:
                cursor.close()
            self.close_conn()

    def get_info_with_username(self, username: str) -> LoginInfo:
        try:
            sql = f'SELECT * FROM user_pwd WHERE username = \"{username}\"'
            conn = self.get_conn()
            cursor = conn.cursor()
            rows = cursor.execute(sql)

            result = None
            for row in rows:
                if result is not None:
                    raise ValueError()
                result = LoginInfo(row[1], row[2], row[0], '')

            if result is None:
                raise ValueError()

            # Generate token
            result.token = self.generate_user_token(result.user_id)

            return result
        except Exception as err:
            log_output(str(err))
        finally:
            if cursor is not None:
                cursor.close()
            self.close_conn()

    def get_username_with_id(self, user_id: int) -> str:
        try:
            sql = f'SELECT username FROM user_pwd WHERE user_id = {user_id}'
            conn = self.get_conn()
            cursor = conn.cursor()
            rows = cursor.execute(sql)

            result = None
            for row in rows:
                if result is not None:
                    raise ValueError()
                result = row[0]

            if result is None:
                raise ValueError()

            return result
        except Exception as err:
            log_output(str(err))
        finally:
            if cursor is not None:
                cursor.close()
            self.close_conn()

    def register_user_pwd(self, username: str, password: str) -> LoginInfo:
        try:
            sql = f'SELECT * FROM user_pwd WHERE username = \"{username}\"'
            conn = self.get_conn()
            cursor = conn.cursor()
            rows = cursor.execute(sql)

            result = None
            for row in rows:
                if result is not None:
                    raise ValueError()
                result = LoginInfo(row[1], row[2], row[0], '')

            if result is not None:
                # Username has been used
                raise ValueError()

            cur_user_id = self.user_cnt

            sql = f'INSERT INTO user_pwd VALUES ({cur_user_id},\"{username}\",\"{password}\")'
            cursor.execute(sql)
            conn.commit()

            self.user_cnt += 1

            token = self.generate_user_token(cur_user_id)
            result = LoginInfo(username, password, cur_user_id, token)

            return result
        except Exception as err:
            log_output(str(err))
            raise ValueError()
        finally:
            if cursor is not None:
                cursor.close()
            self.close_conn()

    @staticmethod
    def random_hex(length):
        result = hex(random.randint(0, 16**length)).replace('0x', '').upper()
        if(len(result) < length):
            result = '0'*(length-len(result))+result
        return result

    def generate_user_token(self, user_id: int) -> str:
        random_token = self.random_hex(length=32)
        try:
            sql = f'DELETE FROM user_token WHERE user_id = {user_id}'
            conn = self.get_conn()
            cursor = conn.cursor()
            cursor.execute(sql)

            sql = f'INSERT INTO user_token VALUES (\"{random_token}\",{user_id})'
            cursor.execute(sql)
            conn.commit()

            return random_token
        except Exception as err:
            log_output(str(err))
            raise ValueError()
        finally:
            if cursor is not None:
                cursor.close()
            self.close_conn()

    def get_user_id_with_token(self, token: str) -> int:
        try:
            sql = f'SELECT user_id FROM user_token WHERE token = \"{token}\"'
            conn = self.get_conn()
            cursor = conn.cursor()
            rows = cursor.execute(sql)

            result = None
            for row in rows:
                result = row[0]

            if result is None:
                raise ValueError("Token not exist")

            return result
        except Exception as err:
            log_output(str(err))
            raise ValueError()
        finally:
            if cursor is not None:
                cursor.close()
            self.close_conn()
